// check pointer
owl_bool owl_check_bytarr_pointer(owl_byte * src, owl_umax size)
{
  return (src != NULL && size >= sizeof(owl_bytarr));
}

// check members
owl_bool owl_check_bytarr_members(owl_byte * ptr, owl_umax size)
{
  // reuse owl_check_mem_space_members()
  return owl_check_mem_space_members(ptr, size);
}

// check all
owl_bool owl_check_bytarr_all(owl_bytarr * data, owl_umax size)
{
  return owl_check_bytarr_pointer((void *) data, size) 
         && owl_check_bytarr_members(data -> ptr, data -> size);
}

// create a bytarr structure
owl_bytarr * owl_create_bytarr(owl_byte * data, owl_umax size)
{
  // check parameters (only the size)
  if (size == 0)
    return NULL;
  
  // otherwise create the structure
  owl_bytarr * rtn = (owl_bytarr *) owl_create_mem_space(size);
  if (data != NULL) // copy the bytes from data into the structure
    for (owl_umax i = 0; i < size; i++)
      (rtn -> ptr)[i] = data[i];
  return rtn;
}

// function to free a bytarr structure
owl_umax owl_free_bytarr(owl_bytarr * arr)
{
  // check params
  if (owl_check_bytarr_all(arr, sizeof(owl_bytarr)) == owl_false)
    return 0;
  return owl_free_mem_space((owl_mem_space *) arr);
}

// function to print a bytarr structure
owl_umax owl_print_bytarr(owl_byte * src, owl_umax size)
{
  // check params
  printf("OWL_BYTARR struct info:\n");
  printf("  Struct info: %p, %llu\n", src, size);
  if (owl_check_bytarr_pointer(src, size) == owl_false)
    return 0;
  
  // otherwise print the contents of the bytarr
  owl_bytarr * arr = (void *) src;
  printf("  Array pointer: %p\n", arr -> ptr);
  printf("  Array size (bytes): %llu\n", arr -> size);
  printf("  Array contents (hex): ");
  for (owl_umax i = 0; i < (arr -> size); i++) {
    //~ owl_print_char_char((arr -> ptr) + i, (arr -> size) - i);
    owl_print_byte_hex((arr -> ptr) + i, (arr -> size) - i);
    putchar(' ');
  }
  putchar('\n');
  return sizeof(owl_bytarr);
}

// compare 2 bytarrs
owl_char owl_comp_bytarr(owl_byte * arr1, owl_byte * arr2)
{
  // check params
  owl_bytarr * arr1_tmp = (void *) arr1,
             * arr2_tmp = (void *) arr2;
  // check params
  if (owl_check_bytarr_all(arr1_tmp, sizeof(owl_bytarr)) == owl_false
      || owl_check_bytarr_all(arr2_tmp, sizeof(owl_bytarr)) == owl_false)
      return owl_comp_err;
  // do the comparison, size and byte contents matter
  // size has higher meaning than byte content
  // size
  if (arr1_tmp -> size < arr2_tmp -> size)
    return owl_comp_less;
  else if (arr1_tmp -> size > arr2_tmp -> size)
    return owl_comp_greater;
  // byte comparison if sizes are equal
  // first byte comparison to trigger the < or > expressions
  for (owl_umax i = 0; i < arr1_tmp -> size; i++) {
    if ((arr1_tmp -> ptr)[i] < (arr2_tmp -> ptr)[i])
      return owl_comp_less;
    else if ((arr1_tmp -> ptr)[i] > (arr2_tmp -> ptr)[i])
      return owl_comp_greater;
  }
  // legit real equal bytarrs
  return owl_comp_equal;
}

// function to copy a bytarr into another bytarr 
owl_bytarr owl_copy_bytarr(owl_bytarr * src, owl_bytarr * dest)
{
  // check params
  if (owl_check_bytarr_all(src, sizeof(owl_bytarr)) == owl_false
      || owl_check_bytarr_all(dest, sizeof(owl_bytarr)) == owl_false
      || (src -> size) > (dest -> size))
    return (owl_bytarr) {NULL, 0};
  
  // copy the byte contents of a bytarr into the other
  for (owl_umax i = 0; i < src -> size; i++)
    (dest -> ptr)[i] = (src -> ptr)[i];
  
  // return the last write position on dest as a bytarr struct
  return (owl_bytarr) {(dest -> ptr) + (src -> size),
                       (dest -> size) - (src -> size)};
}

// get the integer index at which 2 bytarrs start not being equal
owl_umax owl_get_bytarr_mismatch_index(owl_bytarr * arr1, owl_bytarr * arr2)
{
  // check params (arr1 -> size will be used for error checking)
  if (owl_check_bytarr_all(arr1, sizeof(owl_bytarr)) == owl_false
      || owl_check_bytarr_all(arr2, sizeof(owl_bytarr)) == owl_false)
      return arr1 -> size;
  
  // else do comparisons until a mismatch
  // get max bytarr size
  owl_umax low_size = arr1 -> size < arr2 -> size ? arr1 -> size : arr2 -> size;
  for (owl_umax i = 0; i < low_size; i++)
    if ((arr1 -> ptr)[i] != (arr2 -> ptr)[i])
      return i;
  // in case the tiniest bytarr is contained within the larger one
  return arr1 -> size;
}

// function to ignore certain types of bytes from a bytarr and 
// return the position in which there are no more bytes to skip 
owl_bytarr owl_ignore_bytarr_bytes(owl_bytarr * arr, owl_bytarr * skip)
{
  // check params (arr -> size will be used for error checking)
  if (owl_check_bytarr_all(arr, sizeof(owl_bytarr)) == owl_false
      || owl_check_bytarr_all(skip, sizeof(owl_bytarr)) == owl_false)
    return (owl_bytarr) {NULL, 0};

  // otherwise, return the integer index
  owl_bool keep_skipping = owl_false;
  for (owl_umax i = 0; i < arr -> size; i++)
  {
    // compare i's element from arr with all the elements from skip
    for (owl_umax j = 0; j < skip -> size; j++)
      keep_skipping = keep_skipping || ((arr -> ptr)[i] == (skip -> ptr)[j]);
    // if none of the bytes from skip matches the i's byte from arr the return the i index
    if (keep_skipping == owl_false)
      return (owl_bytarr) {arr -> ptr + i, arr -> size - i};
    // otherwise, reset the boolean
    keep_skipping = owl_false;
  }  
  // in case all the bytes from arr can be found in skip
  return (owl_bytarr) {NULL, 0};
}

// function to search if a bytarr is found inside another one
owl_bytarr owl_search_bytarr(owl_bytarr * src, owl_bytarr * search)
{
  // check params (src -> size will be used for error checking)
  if (owl_check_bytarr_all(src, sizeof(owl_bytarr)) == owl_false
      || owl_check_bytarr_all(search, sizeof(owl_bytarr)) == owl_false
      || src -> size < search -> size)
    goto err;
    
  // use owl_comp_bytarr() until a match is found
  owl_bytarr tmp = {src -> ptr, search -> size};
  for (owl_umax i = 0; i <= (src -> size - search -> size); i++) {
    // return the i index when a match is found
    owl_bool cmp = owl_comp_bytarr((void *) &tmp, (void *) search);
    if (cmp == owl_comp_equal)
      return (owl_bytarr) {src -> ptr + i, src -> size - i};
    else if (cmp == owl_comp_err)
      goto err;
    else
      (tmp.ptr)++;
  }
  
  // no match found
  err:
  return (owl_bytarr) {NULL, 0};
}

// function to reverse the bytes in a bytarr
owl_umax owl_reverse_bytarr(owl_bytarr * arr)
{
  // check params
  if (owl_check_bytarr_all(arr, sizeof(owl_bytarr)) == owl_false)
    return 0;
  
  // reverse the bytes
  owl_umax middle_pos = arr -> size / 2;
  for (owl_umax i = 0, tmp = 0; i < middle_pos; i++) {
    tmp = (arr -> ptr)[i];
    (arr -> ptr)[i] = (arr -> ptr)[arr -> size - i - 1];
    (arr -> ptr)[arr -> size - i - 1] = tmp;
  }
  // reverse op was done (return the number of bytes moved)
  return arr -> size;
}

// check if a byteshift integer is valid
owl_bool owl_check_byteshift_dir(owl_byteshift_dir shift)
{
  return (shift == OWL_BYTESHIFT_LEFT || shift == OWL_BYTESHIFT_RIGHT);
}

// make a simple shift on the bytes of data
owl_umax owl_bytarr_shift(owl_bytarr * arr, owl_byteshift_dir shift)
{
  // check params
  if (owl_check_bytarr_all(arr, sizeof(owl_bytarr)) == owl_false
      || owl_check_byteshift_dir(shift) == owl_false)
    return 0;
  
  // decide the byteshift direction
  owl_char increment = 1;
  owl_umax arr_index = 0;
  if (shift == OWL_BYTESHIFT_LEFT) {
    increment = -1;
    arr_index = arr -> size - 1;
  }
  // do the byteshift
  owl_byte tmp1 = 0, tmp2 = 0;
  for (owl_umax i = 0; i < arr -> size; i++) {
    tmp1 = (arr -> ptr)[arr_index];
    (arr -> ptr)[arr_index] = tmp2;
    tmp2 = tmp1;
    arr_index += increment;
  }
  // return the number of bytes read
  return arr -> size;
}

// just call owl_bytarr_shift() n times
owl_umax owl_bytarr_n_shift(owl_bytarr * arr, owl_byteshift_dir shift, owl_umax n)
{
  // check params
  if (owl_check_bytarr_all(arr, sizeof(owl_bytarr)) == owl_false
      || owl_check_byteshift_dir(shift) == owl_false
      || n == 0)
    return 0;
  
  // do the byteshift
  owl_umax tmp = n;
  while (n-- != 0)
    owl_bytarr_shift(arr, shift);
  return tmp * (arr -> size);
}

// function to do a circular byteshift on a bytarr
owl_umax owl_bytarr_circ_shift(owl_bytarr * arr, owl_byteshift_dir shift)
{
  // check params
  if (owl_check_bytarr_all(arr, sizeof(owl_bytarr)) == owl_false
      || owl_check_byteshift_dir(shift) == owl_false)
    return 0;
  
  // get the byte that will get lost after a call to owl_bytarr_shift
  owl_byte tmp = (arr -> ptr)[arr -> size - 1];
  if (shift == OWL_BYTESHIFT_LEFT)
    tmp = (arr -> ptr)[0];
  // do the byteshift
  owl_bytarr_shift(arr, shift);
  // place the byte where it should be after the circular shift
  if (shift == OWL_BYTESHIFT_RIGHT)
    (arr -> ptr)[0] = tmp;
  else
    (arr -> ptr)[arr -> size - 1] = tmp;
  // return the number of bytes read
  return arr -> size;
}

// function to call n times owl_bytarr_circ_shift()
owl_umax owl_bytarr_n_circ_shift(owl_bytarr * arr, owl_byteshift_dir shift, owl_umax n)
{
  // check params
  if (owl_check_bytarr_all(arr, sizeof(owl_bytarr)) == owl_false
      || owl_check_byteshift_dir(shift) == owl_false
      || n == 0)
    return 0;
  
  // do the circular byteshift
  owl_umax tmp = n;
  while (n-- != 0)
    owl_bytarr_circ_shift(arr, shift);
  return tmp * (arr -> size);
}

// function to insert a bytarr inside another one
owl_bytarr * owl_ins_bytarr(owl_bytarr * arr, owl_umax arr_ins_pos, owl_bytarr * insert)
{
  // check params
  owl_bytarr * new = NULL;
  if (owl_check_bytarr_all(arr, sizeof(owl_bytarr)) == owl_false
      || arr_ins_pos > arr -> size
      || owl_check_bytarr_all(insert, sizeof(owl_bytarr)) == owl_false)
    goto err;
  
  // otherwise allocate memory for the new array
  new = owl_create_bytarr(NULL, arr -> size + insert -> size);
  if (new == NULL)
    goto err;
  
  // copy the contents from arr into new until arr_ins_pos
  // then copy the insert byte data and after copy the rest of the bytes on arr
  owl_bytarr tmp1 = *arr, tmp2 = *arr;
  // consider the case in which insertion occurs at the very beginning
  if (arr_ins_pos != 0) {
    tmp1.size = arr_ins_pos;
    tmp1 = owl_copy_bytarr(&tmp1, new);
    if (tmp1.ptr == NULL)
      goto err;
  }
  else
    tmp1 = *new;
  // insert the bytes
  tmp1 = owl_copy_bytarr(insert, &tmp1);
  if (tmp1.ptr == NULL)
    goto err;
  // consider the case in which the bytes are added at the very end
  if (arr_ins_pos != arr -> size) {
    tmp2.ptr += arr_ins_pos;
    tmp2.size = tmp1.size;
    tmp1 = owl_copy_bytarr(&tmp2, &tmp1);
    if (tmp1.ptr == NULL)
      goto err;
  }
  
  // all done, return the new structure
  return new;
  err:
  // free the allocated memory if something wrong happens
  owl_free_bytarr(new);
  return NULL;
}

// function to remove bytes from a bytarr
owl_umax owl_rm_bytarr(owl_bytarr * arr, owl_umax rm_pos, owl_umax rm_size)
{
  // check params
  if (owl_check_bytarr_all(arr, sizeof(owl_bytarr)) == owl_false
      || rm_size == 0 || rm_size > arr -> size - (rm_pos + 1))
    return 0;
  
  // use bytarr_shift to remove the unwanted bytes
  owl_bytarr tmp = {arr -> ptr + rm_pos, arr -> size - (rm_pos + 1)};
  owl_bytarr_n_shift(&tmp, OWL_BYTESHIFT_LEFT, rm_size);
  return (arr -> size -= rm_size);
}
